home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr49 / 296_01.zip / HAVENER.TXT < prev    next >
Text File  |  1993-04-01  |  16KB  |  271 lines

  1. Rapid Prototyping as a Design Method - Building a C to C++ Migrator
  2.  
  3. by Charles D. Havener
  4.  
  5. Author: Senior Principal Engineer
  6.  
  7. GenRad Inc. MS 1A
  8.  
  9. 300 Baker Avenue
  10.  
  11. Concord Mass. 01742
  12.  
  13. Masters degrees in electrical engineering from Cornell University and in
  14. Computer Science from Boston University. Instructor in C in the
  15. Northeastern University State of the Art program. My work at GenRad is
  16. designing and implementing control software in C for automatic component
  17. and printed circuit board test equipment. 
  18.  
  19. INTRODUCTION 
  20.  
  21. This article advocates the conscious use of rapid prototyping as a
  22. powerful technique to be used in the software design process. A working
  23. prototype of a C to C++ migrator tool is a concrete example of how to
  24. build prototypes for text or computer language processing problems. The
  25. migrator tool is intended to automate some of the tasks in porting old
  26. style C code to the new ANSI style or C++ style that requires function
  27. prototypes. The goal is to automatically extract prototypes for use in
  28. header files and to edit the old style C into a new file that uses
  29. prototypes. Any extern function declarations should be edited out by
  30. the tool since they will presumably be in header files. 
  31.  
  32. RAPID PROTOTYPING - GOALS AND PHILOSOPHY
  33.  
  34. In his paper "No Silver Bullets" Fred Brooks ( author of the software
  35. classic, The Mythical Man-Month ) states that "one of the most promising
  36. of the current technological efforts, and one that attacks the essence,
  37. not the accidents, of the software problem, is the development of
  38. approaches and tools for rapid prototyping of systems, as prototyping is
  39. part of the iterative specification of requirements."[1] The traditional
  40. waterfall diagram of design includes the following steps; Requirements -
  41. Specification - Design. Most real world programs are complex enough
  42. that some iterative steps around the requirements and specifications are
  43. needed to do the design. Thus the following is a better model;
  44.  
  45.  Requirements ->- Specification --> Design  ---> Develop --->
  46.  
  47.       ^                               v
  48.       |                               |
  49.       -------- Prototype <-------------
  50.  
  51. Prototyping helps us understand a complex problem in greater depth, it
  52. permits exploring different design approaches, it can provide early
  53. warning of unexpected difficulties, it can overcome the paralysis that
  54. sometimes sets in when you don't know enough about a problem to do a
  55. good design, and it raises morale by providing a working system early. 
  56. A conscious recognition that our designs will follow the prototyping
  57. model leads us to collect software tools and code that can be used in
  58. this process which reinforces its effectiveness over time. Prototyping
  59. is exploratory in nature. It is important not to get sidetracked into
  60. perfecting some particular aspect of the design. The idea is to drive
  61. as deeply as possible, to unearth problem areas. Reuse old code,"don't
  62. let the work of others evade your eyes, plagiarize", lash things
  63. together with Unix shell scripts or DOS batch files. Don't bother with
  64. elaborate error handling. Use tools. 
  65.  
  66. There are several commercial tools for prototyping user interfaces such
  67. as Dan Bricklin's Demo Program. The migrator program deals with text,
  68. i.e. computer language program source code. Lex and YACC work-alikes
  69. are widely available tools that are invaluable for prototypes that
  70. involve language processing. These tools were created to help build
  71. compilers and translators by automatically creating scanners and parsers
  72. but they have many other uses. This article assumes the reader is
  73. familiar with these tools or will be motivated to master them by what
  74. follows. The migrator was originally developed on a Sun workstation and
  75. then ported to PC-DOS using the MKS YACC (reviewed in the April 1989
  76. issue of the C Users Journal) and the new public domain flex, a public
  77. domain re-implementation of lex with improvements. If you obtain the
  78. flex sources ( e.g. from the Austin Code Works ) and port them to the
  79. PC, there is one tricky part in addition to the extra long variable
  80. names. Be sure that the declarations for the extern variable yytext
  81. agree in both the YACC and lex that you use. If one uses char yytext[],
  82. and the other uses extern char* yytext, then it will not work. 
  83.  
  84. THE C TO C++ MIGRATOR
  85.  
  86. Listing 1 shows sample input and output files from the migrator tool and
  87. Figure 1 is an overview of how the migrator is put together and
  88. controlled. The source code accompanying this article is a snapshot of
  89. the present state of the migrator prototype. The remainder of this
  90. article covers the rapid prototyping process phase by phase as the
  91. migrator tool was 'grown' over a period of several days. 
  92.  
  93. A search was conducted to see if a migrator tool or function prototype
  94. extractor was available. There was some activity on the Usenet in the
  95. C++ news group about such tools but the only one posted required access
  96. to the lint program sources on Unix. A modified lint could be created
  97. that would produce prototypes. Nothing was found that would do the
  98. complete job, though it was rumored that some were under development. 
  99.  
  100. FINDING A C GRAMMAR
  101.  
  102. There is a 480 line C grammar that was posted several times to the
  103. Usenet. The migrator uses this grammar which was developed by Jeff Lee
  104. at Georgia Tech based on April 1985 ANSI C committee drafts provided by
  105. Arnold Robbins. Many of the commercial YACC tools come with several
  106. example grammars, e.g PCYACC comes with a 518 line C grammar, as well as
  107. C++ and Pascal grammars. These can be worth the price of the software
  108. alone for use in prototyping things like C++ class browsers etc. The
  109. public C grammar generates parsers that accept various illegal C
  110. statements such as "Hello World"++, --1.23, and *'a' according to Jeff
  111. Lee but for our application it doesn't matter. Presumably we will be
  112. providing C code to the migrator that has been validated by a true
  113. compiler. The first step in the migrator prototyping was to use the C
  114. grammar and lex specification to create a parser that would accept a C
  115. program. The idea was to add the semantic action routines to the
  116. grammar in the places required to produce the migrator. The grammar
  117. requires that the C source code has been preprocessed by the standard
  118. cpp to remove #include etc lines and to expand all macros. For the
  119. prototype migrator a shell script was used that first fed the C source
  120. code through the cpp and then to the migrator. Listings 2 & 3 are the
  121. lex input file and the relevant parts of the grammar. ( Source code
  122. available for this article from the C Users Journal includes the full
  123. grammar ). 
  124.  
  125. SOLVING THE TYPEDEF PROBLEM
  126.  
  127. The C grammar provided will not accept C programs that use typedef. 
  128. This was the first hurdle to overcome and it was mentioned as a problem
  129. several times on the Usenet. The difficulty arises because the lexical
  130. analysis phase or scanner cannot distinguish a typedef identifier from
  131. any other variable identifier without a symbol table. Thus the next
  132. step was to add a symbol table and the appropriate code to use it. A
  133. simple hashing symbol table module is in the the listing symtab.c. I
  134. have used this code with minor modifications many times. A symbol table
  135. module is something every devotee of rapid prototyping should have
  136. handy. It does the standard things, it has an initsymtab(), storesym()
  137. and findsym() interfaces. Actions were added ,within braces, to the
  138. grammar to store typedef symbols into the table. In the production
  139. "declaration : TYPEDEF ;" a global flag in_tdef was set on the
  140. assumption that the next IDENTIFIER encountered would be a typedef name. 
  141. The "identifier: IDENTIFIER" production at the end of the grammar makes
  142. use of the global flag to store the typedef names into the symbol table. 
  143. Later on, the lexer looks up every identifier it finds and if it is in
  144. the table it returns TYPE_NAME to the parser rather than IDENTIFER. 
  145. (Later the global symbols su and enumflag were added to handle the
  146. identifier names associated with structs or unions and enumerated data.)
  147. At this point the migrator would accept without complaint most valid C
  148. program. It didn't do anything of course, and its only complaint was
  149. syntax error if something went wrong. The only concession to syntax
  150. error assistance was in the count() function in the ctocxx.l listing. 
  151. This updates a global count variable so the yyerror() function called by
  152. yacc can report which column of the input line it gave up on. If the
  153. ECHO macro line is not commented out, the count() function also copies
  154. the input to the standard output to provide more complete information. 
  155.  
  156. EXTRACTING FUNCTION PROTOTYPES
  157.  
  158. Extracting and building function prototypes for output to a proto.out
  159. file was the next goal. Since the lexer passed all input text through
  160. the count() function, a call to a new function called stuff() was added
  161. there. The idea was to use the appropriate grammar rules to set a
  162. global variable that the stuff() function could use to build up a
  163. function prototype from the immediate stream of text that it had saved. 
  164. The listing subs.c contains the stuff() function. It uses a ring buffer
  165. of about 2000 characters to remember the text stream. The stuff()
  166. function in the listing subs.c grew in an ad hoc way from a very small
  167. thing into a monster as functionality was added. The stuff() function
  168. was at the heart of the experimenting and learning process. It may be
  169. possible to have the grammar do more of the work in extracting the
  170. elements needed to build up the function prototypes. However, a
  171. decision was made early in the rapid prototyping to minimize the grammar
  172. actions and to see how much could be done by applying heuristic rules to
  173. the text saved in the ring buffer. 
  174.  
  175. The function prototype was built up in the func_proto[] buffer character
  176. by character as the stuff() function outer while loop stepped through
  177. the current token's text in yytext[]. Each little if section handles
  178. some situation that rapid prototyping uncovered. For example, the
  179. register keyword may appear in old style function argument declarations
  180. but not in function prototypes so it had to be discarded. If the
  181. argument declaration style 'int a,b,c' was used, the root e.g. int had
  182. to be saved and prepended to each argument to make a valid function
  183. prototype e.g. 'int func(int a,int b,int c);'. There are some
  184. functions that don't need a function prototype, e.g. main() and any
  185. function declared to be static. Finally, note that old style
  186. declarations often defaulted to int but this must be added for function
  187. prototypes. The symbol table is useful here. Just before the newly
  188. built prototype is written out, the first word is looked up in the
  189. table. If it isn't there, 'int' is prepended. 
  190.  
  191. BUILDING THE SED SCRIPT
  192.  
  193. The next step was to provide a means to automatically edit the original
  194. file into the desired form. One possible design would be to make the
  195. migrator edit the input on the fly and write it out. This was
  196. considered and rejected as too much work for a prototype. After all,
  197. the text had already gone by before we could figure out how to alter it. 
  198. This would mean a delay buffer between input and output. At first it
  199. seemed we could write out some edit commands for the unix 'ed' line
  200. editor but that editor renumbered all the lines of text everytime a
  201. delete was done. The sed stream editor worked perfectly. Sed accepts
  202. simple delete and insert commands based on line numbers and fortunately
  203. the cpp preprocessor inserts '# line' code into the program so we can
  204. always tell what line we are on. The pound() function in the ctocxx.l
  205. file listing takes care of this when the lexer matches a '#line' in the
  206. input text. The sed edit script commands are written out to the ed.out
  207. file for later use by the shell script or batch file that drives the
  208. migrator. Sed has one minor problem, it holds the entire script in
  209. memory at once and for very large complex C files it was possible to
  210. exceed sed's command buffer. Much of the complexity in the ed_delete(),
  211. ed_flush() and elsewhere in the subs.c listing is to minimize the size
  212. of the sed script produced. The grammar tended to produce line delete
  213. commands such as 5d 6d 7d 8d. By delaying the command output it was
  214. possible to put out 5,8d commands which saved space in the sed command
  215. buffer. 
  216.  
  217. The write_proto() function in the subs.c listing is called from the
  218. declarator2 production rule of the grammar. At this point the parser
  219. has encountered what may be the beginning of a function declaration. It
  220. may be an extern function which must be ignored, or a function with no
  221. arguments, or one with several arguments. The global proto_flag is set
  222. true so the stuff() function will add the arg declarations to the
  223. func_proto buffer as it finds them. But first, the write_proto()
  224. function must back up over the input text to the beginning of the
  225. function declaration. It uses a character stack embodied by the push()
  226. and pop() functions to save chars as it backs up through the ring
  227. buffer. One coding style puts the function return type declaration on
  228. the preceding line, e.g. the crazy() function in the example listing. 
  229. The migrator handles this but it tends to delete extra blank lines used
  230. for spacing. 
  231.  
  232. STATE OF THE MIGRATOR - WHAT WAS LEARNED
  233.  
  234. A few hundred thousand lines of C has been successfully passed through
  235. the migrator in it's current state and while it is quite useful it is
  236. not a panacea for porting old style code. Typically the converted code
  237. has harmless type mismatches that will no longer compile. In some cases
  238. the old style compiler would accept syntax that is illegal and the
  239. migrator would not accept it. The port to DOS required the addition of
  240. the non-standard keywords near,far, and decl to the ctocxx.l file. 
  241. Presently they are ignored, the migrator must be enhanced to deal with
  242. them for code that uses them. Furthermore, while comma separated
  243. function argument lists are accepted, comma separated typedef lists are
  244. not. A potentially major problem when moving C code to C++ is that
  245. declarations like 'struct foo foo' are illegal. In C++ the variable and
  246. structure tag name space are the same so the names must be different. 
  247. This seems like something that should be fixed manually and not by an
  248. automatic tool like the migrator. 
  249.  
  250. The migrator prototype has revealed some features that are desirable to
  251. support in a finished product. For example, the function argument names
  252. are currently retained in the prototypes. This tends to make for very
  253. long lines and since they are ignored by the compiler there should be an
  254. option to produce function prototypes without argument names. Code that
  255. contains #ifdef sections with functions inside may be elided by the cpp. 
  256. The migrator will not produce function prototypes or editing scripts for
  257. those sections of code. It may take several passes through the migrator
  258. with different conditions set to make all the changes. Another minor
  259. issue is that some code is written in a style that uses #defines rather
  260. than typedefs to make pseudo types. For example #define COUNT int. The
  261. C preprocessor removes all instances of COUNT so the migrator will not
  262. see it and pass it through. The prototype migrator works reasonably
  263. well and it could be cleaned up to be a production quality tool. The
  264. sed editing is simple enough that it could easily be folded back into
  265. the migrator. The simple script or .bat controller functions could be
  266. pulled into the main() function by using various command line options. 
  267.  
  268. [1] "No Silver Bullets" by Frederick P. Brooks Jr. Unix Review, Nov
  269. 1987 pp 39-48, or IEEE Computer Magazine April 1987. 
  270.  
  271.